using System;
using System.Collections.Generic;
using System.Reflection;

using Pegasus.Diagnostics;
using Pegasus.Log4Net;
using Pegasus.Workflow.Service.DefaultServices;

namespace Pegasus.Workflow.Service
{
	/// <summary>
	/// The root object for the workflow service.
	/// </summary>
	public class WorkflowService : IServiceProvider
	{
		// Local Instance Values
		private ILog m_log = LogManager.GetLogger( typeof( WorkflowService ) );
		private bool m_running = false;
		private MethodInfo m_dataExchangeMethodInfo = null;

		// You must lock this object before accessing it.
		private Dictionary<Type, object> m_serviceTable = new Dictionary<Type, object>();

		// You must lock this object before accessing it.
		private List<Type> m_workflowTypeList = new List<Type>();

		/// <summary>
		/// Initializes a new instance of the <see cref="T:WorkflowService"/> class.
		/// </summary>
		public WorkflowService()
		{
			// Get our handler method info that will catch all events 
			// from the data exchange services
			DataExchangeProxy dummyDataHandler = new DataExchangeProxy();
			EventHandler<WorkflowEventArgs> dummyEventHandler = 
				new EventHandler<WorkflowEventArgs>( dummyDataHandler.DataExchangeEventHandler );
			
			m_dataExchangeMethodInfo = dummyEventHandler.Method;
		}

		/// <summary>
		/// Gets a value indicating whether this <see cref="T:WorkflowService"/> is running.
		/// </summary>
		/// <value><c>true</c> if running; otherwise, <c>false</c>.</value>
		public bool IsRunning
		{
			get
			{
				return m_running;
			}
		}

		/// <summary>
		/// Starts this instance of the workflow service.
		/// </summary>
		public void Start()
		{
			ThrowIfRunning();

			// If there are not system services then add the default classes
			if( GetService<WorkflowMessageService>() == null )
			{
				RegisterSystemService( new ThreadedMessageService(), typeof( WorkflowMessageService ) );
			}

			if( GetService<WorkflowPersistenceService>() == null )
			{
				RegisterSystemService( new NullPersistenceService(), typeof( WorkflowPersistenceService ) );
			}

			if( GetService<WorkflowTrackingService>() == null )
			{
				RegisterSystemService( new NullTrackingService(), typeof( WorkflowTrackingService ) );
			}

			// Need to build a list of start up services.  This is because system services
			// are registered in the service table more than once under different types.
			List<IWorkflowService> startedList = new List<IWorkflowService>();

			try
			{
				foreach( KeyValuePair<Type, object> item in m_serviceTable )
				{
					// If this is object support the workflow service interface then start it up...
					IWorkflowService workflowService = item.Value as IWorkflowService;
					if( workflowService != null )
					{
						// Did we already start this service
						if( !startedList.Contains( workflowService ) )
						{
							workflowService.Start( this );
							startedList.Add( workflowService );
						}
					}

					// If this is a data exchange service the setup the event bindings
					WorkflowDataExchangeService dataService = item.Value as WorkflowDataExchangeService;
					if( dataService != null )
					{
						SetupDataExchangeService( dataService );
					}
				}
			}
			catch( Exception e )
			{
				PegasusExceptionList exList = new PegasusExceptionList( "Exception starting workflow service" );
				exList.PrimaryException = e;

				// If there is an exception then stop the services that where started.
				foreach( IWorkflowService service in startedList )
				{
					try
					{
						service.Stop();
					}
					catch( Exception ex )
					{
						// Add the exceptions to the list and continue 
						// stopping the rest of the services.
						exList.AddException( ex );
					}
				}

				throw exList;
			}

			m_running = true;
		}

		/// <summary>
		/// Stops this instance of the workflow service
		/// </summary>
		public void Stop()
		{
			ThrowIfNotRunning();

			m_running = false;

			// Need to build a list of stopped services.  This is because system services
			// are registered in the service table more than once under different types.
			List<IWorkflowService> stoppedList = new List<IWorkflowService>();

			// Just in case we have any exceptions
			PegasusExceptionList exList = new PegasusExceptionList( "Exception stopping workflow service" );

			// Stop all the serivces
			foreach( KeyValuePair<Type, object> item in m_serviceTable )
			{
				try
				{
					IWorkflowService workflowService = item.Value as IWorkflowService;
					if( workflowService != null )
					{
						if( !stoppedList.Contains( workflowService ) )
						{
							workflowService.Stop();
							stoppedList.Add( workflowService );
						}
					}
				}
				catch( Exception e )
				{
					exList.AddException( e );
				}
			}

			// Only throw the exception list if we have one or more exceptions.
			if( exList.Count > 0 )
			{
				throw exList;
			}
		}

		/// <summary>
		/// Registers the service.
		/// </summary>
		/// <param name="service">The service.</param>
		public void RegisterService( object service )
		{
			// Check Parameters
			ParamCode.AssertNotNull( service, "service" );

			ThrowIfRunning();

			lock( m_serviceTable )
			{
				Type serviceType = service.GetType();
				if( !m_serviceTable.ContainsKey( serviceType ) )
				{
					// Is the object a system or data exhcange service.  
					// If so then add it to the table under the base class.
					if( service is WorkflowMessageService )
					{
						RegisterSystemService( service, typeof( WorkflowMessageService ) );
					}
					else if( service is WorkflowPersistenceService )
					{
						RegisterSystemService( service, typeof( WorkflowPersistenceService ) );
					}
					else if( service is WorkflowTrackingService )
					{
						RegisterSystemService( service, typeof( WorkflowTrackingService ) );
					}

					// Add the service under it's own time
					m_serviceTable.Add( serviceType, service );
				}
				else
				{
					throw new WorkflowException( "This service type {0} is already registered", serviceType );
				}
			}
		}

		/// <summary>
		/// Gets the service.
		/// </summary>
		/// <returns></returns>
		public T GetService<T>()
		{
			return (T) GetService( typeof( T ) );
		}

		/// <summary>
		/// Gets the service object of the specified type.
		/// </summary>
		/// <param name="serviceType">An object that specifies the type of service object to get.</param>
		/// <returns>
		/// A service object of type serviceType.-or- null if there is no service object of type serviceType.
		/// </returns>
		public object GetService( Type serviceType )
		{
			// Check Parameters
			ParamCode.AssertNotNull( serviceType, "serviceType" );
			lock( m_serviceTable )
			{
				if( m_serviceTable.ContainsKey( serviceType ) )
				{
					return m_serviceTable[ serviceType ];
				}

				return null;
			}
		}

		/// <summary>
		/// Registers the workflow.
		/// </summary>
		/// <param name="workflowType">Type of the workflow.</param>
		public void RegisterWorkflow( Type workflowType )
		{
			// Check Parameters
			ParamCode.AssertNotNull( workflowType, "workflowType" );
			ParamCode.Assert( workflowType.IsSubclassOf( typeof( Workflow ) ), "workflowType", "Workflow type {0} is not subclass of workflow", workflowType );

			ThrowIfRunning();

			lock( m_workflowTypeList )
			{
				if( !m_workflowTypeList.Contains( workflowType ) )
				{
					m_workflowTypeList.Add( workflowType );
				}
				else
				{
					throw new WorkflowException( "Workflow type {0} is already registered", workflowType );
				}
			}
		}

		/// <summary>
		/// Starts the workflow.
		/// </summary>
		/// <param name="workflowType">Type of the workflow.</param>
		/// <param name="args">The args.</param>
		/// <returns></returns>
		public int StartWorkflow( Type workflowType, params object[] args )
		{
			return StartWorkflowEx( workflowType, null, args );
		}

		/// <summary>
		/// Gets a read-only copy of the workflow context.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <returns></returns>
		public WorkflowContext GetWorkflow( int workflowId )
		{
			return GetWorkflow( workflowId, true );
		}

		/// <summary>
		/// Gets the workflow.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <param name="readOnly">if set to <c>true</c> [read only].</param>
		/// <returns></returns>
		public WorkflowContext GetWorkflow( int workflowId, bool readOnly )
		{
			// Check Parameters
			ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );

			return GetService<WorkflowPersistenceService>().LoadWorkflowContext( workflowId, readOnly );
		}

		/// <summary>
		/// Fires the unhandled exception.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The e.</param>
		public void FireUnhandledException( object sender, Exception e )
		{
			if( UnhandledException != null )
			{
				UnhandledException( sender, e );
			}
		}

		/// <summary>
		/// Starts the workflow.
		/// </summary>
		/// <param name="workflowType">Type of the workflow.</param>
		/// <param name="completedHandler">The completed handler.</param>
		/// <param name="args">The args.</param>
		/// <returns></returns>
		internal int StartWorkflowEx( Type workflowType, EventHandler<WorkflowEventArgs> completedHandler, params object[] args )
		{
			// Check Parameters
			ParamCode.AssertNotNull( workflowType, "workflowType" );
			// completedHandler can be null
			ParamCode.AssertNotNull( args, "args" );

			ThrowIfNotRunning();

			// Make sure we know about this workflow
			lock( m_workflowTypeList )
			{
				if( !m_workflowTypeList.Contains( workflowType ) )
				{
					throw new WorkflowException( "This workflow type {0} is not registered", workflowType );
				}
			}

			// Create a new context object and get the new workflow
			WorkflowContext context = new WorkflowContext( this, workflowType );
			Workflow workflow = context.Workflow;
			int workflowId = 0;

			// Set the workflow to starting and pass startup args
			workflow.Initialize( context, workflowId );
			workflow.SetStartupArgs( args );
	
			// Register the workflow with the persistence service.
			WorkflowPersistenceService persistenceService = GetService<WorkflowPersistenceService>();
			workflowId = persistenceService.RegisterNewWorkflowContext( context );

			GetService<WorkflowTrackingService>().RecordWorkflowStarted( context, args );

			// OK, now that we have come back from the persistenc service we have a
			// valid workflow.  From here on out we have to catch and process
			// all exceptions.
			try
			{
				// If we have a completed event handler then register it with the workflow now
				if( completedHandler != null )
				{
					workflow.RegisterCompleteHandler( completedHandler );
				}

				// Save the context and unlock it.
				persistenceService.SaveWorkflowContext( context, true );

				// Send the start message to the message service to start the 
				// initial state of the workflow.
				GetService<WorkflowMessageService>().SendWorkflowStartMessage( workflowId );
			}
			catch( Exception e )
			{
				// If the workflow is still loaded
				if( context.IsLocked )
				{
					persistenceService.ReleaseWorkflowContext( context );
				}

				context.Workflow.FireUnhandledException( e );
			}

			return workflow.WorkflowId;
		}


		/// <summary>
		/// Registers the system service.
		/// </summary>
		/// <param name="service">The service.</param>
		/// <param name="serviceType">Type of the service.</param>
		private void RegisterSystemService( object service, Type serviceType )
		{
			// Check Parameters
			ParamCode.AssertNotNull( service, "service" );
			ParamCode.AssertNotNull( serviceType, "serviceType" );

			lock( m_serviceTable )
			{
				// Make sure we only have one service of this type.
				if( !m_serviceTable.ContainsKey( serviceType ) )
				{
					// Add the service under the base class type, This way the system service
					// objects can are available in a generic way without having the workflows
					// know about the specific implementation of each service.
					m_serviceTable.Add( serviceType, service );
				}
				else
				{
					throw new WorkflowException( "A service of type {0} is already registered", serviceType );
				}
			}
		}

		/// <summary>
		/// Binds to data exchange service.
		/// </summary>
		/// <param name="dataExchangeService">The data exchange service.</param>
		private void SetupDataExchangeService( WorkflowDataExchangeService dataExchangeService )
		{
			// Check Parameters
			ParamCode.AssertNotNull( dataExchangeService, "dataExchangeService" );

			Type dataExchangeType = dataExchangeService.GetType();
			EventInfo[] eventList = dataExchangeType.GetEvents();
			foreach( EventInfo eventInfo in eventList )
			{
				// Validate the Handler type
				if( IsValidEventHandler( eventInfo ) )
				{
					// Create an instance of the handler object for this event.
					DataExchangeProxy handlerObject = new DataExchangeProxy( this, dataExchangeType, eventInfo );

					// Create a delegate for the event handler 
					Delegate eventDelegate = Delegate.CreateDelegate( eventInfo.EventHandlerType, handlerObject, m_dataExchangeMethodInfo );

					// Bind the delegate to the event.
					eventInfo.AddEventHandler( dataExchangeService, eventDelegate );
				}
			}
		}

		/// <summary>
		/// Determines whether [is valid event handler] [the specified event type].
		/// </summary>
		/// <param name="eventInfo">The event info.</param>
		/// <returns>
		/// 	<c>true</c> if [is valid event handler] [the specified event type]; otherwise, <c>false</c>.
		/// </returns>
		private bool IsValidEventHandler( EventInfo eventInfo )
		{
			Type handlerType = eventInfo.EventHandlerType;
			if( handlerType.IsGenericType )
			{
				// Bind up the Generic event handler EventHandler<T>
				if( handlerType.GetGenericTypeDefinition().Name == "EventHandler`1" )
				{
					Type[] genericArgs = handlerType.GetGenericArguments();
					if( genericArgs.Length == 1 )
					{
						// Args must be derived from WorkflowEventArgs
						if( genericArgs[ 0 ].IsSubclassOf( typeof( WorkflowEventArgs ) ) )
						{
							// Also must be serializable
							if( genericArgs[ 0 ].IsSerializable )
							{
								m_log.DebugFormat( "DataExchangeService: Binding to event {0} {1}", eventInfo.DeclaringType.Name, eventInfo.Name );
								return true;
							}
							else
							{
								m_log.DebugFormat( "DataExchangeService: Generic argument is not serializable, {0}", genericArgs[ 0 ] );
							}
						}
						else
						{
							m_log.DebugFormat( "DataExchangeService: Generic argument is not a subclass of WorkflowEventArgs, {0}", genericArgs[ 0 ] );
						}
					}
					else
					{
						m_log.DebugFormat( "DataExchangeService: Wrong number of generic types for EventHandler<T> {0}, expecting 1", genericArgs.Length );
					}
				}
				else
				{
					m_log.DebugFormat( "DataExchangeService: Unknown generic type in event info {0}", handlerType );
				}
			}
			else
			{
				MethodInfo invokeInfo = handlerType.GetMethod( "Invoke" );
				if( invokeInfo != null )
				{
					ParameterInfo[] parameters = invokeInfo.GetParameters();
					if( parameters.Length == 2 )
					{
						if( parameters[ 0 ].ParameterType.Equals( typeof( object ) ) )
						{
							if( parameters[ 1 ].ParameterType.IsSubclassOf( typeof( WorkflowEventArgs ) ) )
							{
								// Also must be serializable
								if( parameters[ 1 ].ParameterType.IsSerializable )
								{
									m_log.DebugFormat( "DataExchangeService: Binding to event {0} {1}", eventInfo.DeclaringType.Name, eventInfo.Name );
									return true;
								}
								else
								{
									m_log.DebugFormat( "DataExchangeService: Type for secind parameter is not serializable, {0}", parameters[ 1 ].ParameterType );
								}
							}
							else
							{
								m_log.DebugFormat( "DataExchangeService: Wrong type for second parameter of custom delegate invoke {0}, {1}, expecting subclass of WorkflowEventArgs", handlerType, parameters[ 1 ].ParameterType );
							}
						}
						else
						{
							m_log.DebugFormat( "DataExchangeService: Wrong type for first parameter of custom delegate invoke {0}, {1}, expecting object", handlerType, parameters[ 0 ].ParameterType );
						}
					}
					else
					{
						m_log.DebugFormat( "DataExchangeService: Wrong number of parameters for custom delegate invoke {0}, {1}, expecting 2", handlerType, parameters.Length );
					}
				}
				else
				{
					m_log.DebugFormat( "DataExchangeService: Custom delegate missing Invoke method {0}", handlerType );
				}
			}

			m_log.DebugFormat( "DataExchangeService: Unable to bind to event {0} {1}", eventInfo.DeclaringType.Name, eventInfo.Name );
			return false;
		}


		/// <summary>
		/// Throws if running.
		/// </summary>
		private void ThrowIfRunning()
		{
			if( m_running )
			{
				throw new WorkflowException( "WorkflowService is already started.  The given action must be preformed before the service is running." );
			}
		}

		/// <summary>
		/// Throws if not running.
		/// </summary>
		private void ThrowIfNotRunning()
		{
			if( !m_running )
			{
				throw new WorkflowException( "WorkflowService is has not started.  The given action must be preformed after the service is running." );
			}
		}

		/// <summary>
		/// Creates and object of the given type, using the default constructor.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns></returns>
		internal static object CreateObjectFromType( Type type )
		{
			// Check Parameters
			ParamCode.AssertNotNull( type, "type" );

			ConstructorInfo ctor = type.GetConstructor( new Type[ 0 ] );
			if( ctor != null )
			{
				return ctor.Invoke( null );
			}

			return null;
		}

		/// <summary>
		/// The delegate for the unhandled exceptions.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public delegate void UnhandledExceptionHandler( object sender, Exception e );

		/// <summary>
		/// Fired where the workflow service catches an exception that no one else processed.
		/// </summary>
		public event UnhandledExceptionHandler UnhandledException;
	}
}
